package bma.common.langutil.bean.copy;

import java.text.MessageFormat;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.naming.OperationNotSupportedException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import bma.common.langutil.bean.BeanObjectProperty;
import bma.common.langutil.bean.BeanSystem;
import bma.common.langutil.bean.BeanUtil;
import bma.common.langutil.convert.ConvertManager;
import bma.common.langutil.convert.Converter;
import bma.common.langutil.core.ClassUtil;
import bma.common.langutil.core.ExceptionUtil;
import bma.common.langutil.core.MType;
import bma.common.langutil.core.ValueUtil;
import bma.common.langutil.xdom.Xdom;
import bma.common.langutil.xdom.XdomUtil;
import bma.common.langutil.xdom.core.XdomNULL;

public class BeanCopyTool {

	public static Log log = LogFactory.getLog(BeanCopyTool.class);

	public BeanCopyTool() {
		super();
	}

	// 使用源对象作为复制结构
	private boolean sourceStruct = true;

	private boolean includeAll = true;

	private Map<String, BeanCopyField> fields = new TreeMap<String, BeanCopyField>();

	private boolean ignoreMiss = true;

	public boolean isIgnoreMiss() {
		return ignoreMiss;
	}

	public void setIgnoreMiss(boolean ignoreMiss) {
		this.ignoreMiss = ignoreMiss;
	}

	public boolean isIncludeAll() {
		return this.includeAll;
	}

	public void setIncludeAll(boolean includeAll) {
		this.includeAll = includeAll;
	}

	public Map<String, BeanCopyField> getFields() {
		return this.fields;
	}

	public void setFields(Map<String, BeanCopyField> fields) {
		this.fields = fields;
	}

	public void addField(String name, BeanCopyField field) {
		this.fields.put(name, field);
	}

	public BeanCopyField field(String name) {
		BeanCopyField f = this.fields.get(name);
		if (f == null) {
			f = new BeanCopyField();
			this.fields.put(name, f);
		}
		return f;
	}

	public boolean isSourceStruct() {
		return this.sourceStruct;
	}

	public void setSourceStruct(boolean sourceStruct) {
		this.sourceStruct = sourceStruct;
	}

	public <V extends Object> V newInstance(BeanSystem sys, Object source,
			Class<V> targetClass) {
		if (source == null)
			return null;
		try {
			V target = targetClass.newInstance();
			copy(sys, source, target);
			return target;
		} catch (Exception e) {
			throw ExceptionUtil.throwRuntime(e);
		}
	}

	public Object convert(BeanCopyField field, Object value, Class clazz)
			throws Exception {
		Object o = value;
		try {
			boolean c = false;
			if (value != null && field != null) {
				Converter ct = field.getConverter();
				if (ct != null) {
					if (ct.canConvert(o.getClass(), clazz)) {
						o = ct.convertTo(o, clazz);
						c = true;
					}
				}
			}
			if (!c) {
				o = ConvertManager.getInstance().convert(o, clazz);
				if (o == null) {
					Class type = ClassUtil.toPrimaryClass(clazz);
					o = ValueUtil.getDefaultValue(type);
				}
			}
		} catch (NumberFormatException nfe) {
			o = null;
		} catch (Exception e) {
			if (log.isTraceEnabled()) {
				log.trace(MessageFormat.format("convert {0} to {1} fail", o,
						clazz), e);
			}
			o = null;
		}
		return o;
	}

	public void copy(BeanSystem sys, Object source, Object target) {
		try {
			if (sys == null)
				sys = BeanSystem.getInstance();
			Object sobj = this.sourceStruct ? source : target;
			List<BeanObjectProperty> props = new LinkedList<BeanObjectProperty>();
			BeanUtil.listProperties(sys, sobj.getClass(), props);
			if (props != null) {
				for (BeanObjectProperty prop : props) {
					String name = prop.getName();
					String name2 = name;
					BeanCopyField field = this.fields.get(name);
					if (field != null && ValueUtil.notEmpty(field.getAlias())) {
						name2 = field.getAlias();
					}

					if (field != null) {
						if (field.isExcluded()) {
							continue;
						}
					} else {
						if (!this.includeAll) {
							continue;
						}
					}

					BeanObjectProperty sprop = this.sourceStruct ? prop
							: BeanUtil.getProperty(sys, source.getClass(),
									name2);
					if (sprop == null) {
						String msg = "[" + source.getClass().getName()
								+ "] miss property [" + name2 + "]";
						if (ignoreMiss) {
							if (log.isDebugEnabled()) {
								log.debug(msg);
							}
							continue;
						}
						throw new OperationNotSupportedException(msg);
					}
					Object o = sprop.get(source);
					if (o == null && field != null && field.isNullSkip()) {
						continue;
					}
					BeanObjectProperty tprop = this.sourceStruct ? BeanUtil
							.getProperty(sys, target.getClass(), name2) : prop;
					if (tprop == null) {
						String msg = "[" + target.getClass().getName()
								+ "] miss property [" + name2 + "]";
						if (ignoreMiss) {
							if (log.isDebugEnabled()) {
								log.debug(msg);
							}
							continue;
						}
						throw new OperationNotSupportedException(msg);
					}
					o = convert(field, o, tprop.getType().getCoreClass());
					tprop.set(target, o);
				}
			}
		} catch (Exception e) {
			throw ExceptionUtil.throwRuntime(e);
		}
	}

	@SuppressWarnings("unchecked")
	public void copyXdom(BeanSystem sys, Xdom source, Object target,
			MType helper) {
		try {
			if (source == null)
				return;
			if (target == null)
				return;
			if (sys == null)
				sys = BeanSystem.getInstance();

			switch (source.type()) {
			case ARRAY: {
				if (target instanceof List) {
					List r = (List) target;
					Iterator<Xdom> it = source.arrayIterator();
					while (it.hasNext()) {
						Xdom o = it.next();
						Object v = XdomUtil.createObject(o,
								helper.getCoreClass());
						copyXdom(sys, o, v, helper.getInnerType(0));
						r.add(v);
					}
				}
				break;
			}
			case MAP: {
				Iterator<String> it = source.mapNames();
				if (target instanceof Map) {
					Map r = (Map) target;
					while (it.hasNext()) {
						String n = it.next();
						Xdom o = source.get(n);
						Object v = XdomUtil.createObject(o,
								helper.getCoreClass());
						copyXdom(sys, o, v, helper.getInnerType(1));
						r.put(n, v);
					}
				} else {
					Class cls = target.getClass();
					while (it.hasNext()) {
						String name = it.next();
						String name2 = name;
						Xdom o = source.get(name);

						BeanCopyField field = this.fields.get(name);
						if (field != null
								&& ValueUtil.notEmpty(field.getAlias())) {
							name2 = field.getAlias();
						}
						BeanObjectProperty prop = BeanUtil.getProperty(sys,
								cls, name2);
						if (prop == null) {
							if (log.isDebugEnabled()) {
								String msg = "[" + cls.getName()
										+ "] miss property [" + name2 + "]";
								if (ignoreMiss) {
									if (log.isDebugEnabled()) {
										log.debug(msg);
									}
									continue;
								}
								throw new OperationNotSupportedException(msg);
							}
						}
						if (o == null) {
							if (field != null && field.isNullSkip()) {
								continue;
							} else {
								o = XdomNULL.NULL;
							}
						}
						Object v = XdomUtil.createObject(o, prop.getType()
								.getCoreClass());
						copyXdom(sys, o, v, prop.getType());

						v = convert(field, v, prop.getType().getCoreClass());
						prop.set(target, v);
					}
				}
				break;
			}
			}
		} catch (Exception e) {
			throw ExceptionUtil.throwRuntime(e);
		}
	}
}
